From 8b7d882b8aa9ae9b10c042d7abd9f1c0dc5a95b1 Mon Sep 17 00:00:00 2001 From: rob bygrave Date: Thu, 28 Jun 2018 10:23:34 +1200 Subject: [PATCH] #1442 - Add support to set ebean.dbSchema ... (~ default DB schema) with create-all.sql and DB migrations (so it all just works) --- pom.xml | 4 +- .../io/ebean/config/DbMigrationConfig.java | 34 ++++++++ .../java/io/ebean/config/ServerConfig.java | 28 +++++++ .../config/dbplatform/DatabasePlatform.java | 34 ++++++++ .../dbmigration/DdlGenerator.java | 83 ++++++++++++------- 5 files changed, 152 insertions(+), 31 deletions(-) diff --git a/pom.xml b/pom.xml index a82aec239..e6b9650f0 100644 --- a/pom.xml +++ b/pom.xml @@ -135,7 +135,7 @@ io.ebean ebean-migration - 11.5.4 + 11.6.1 @@ -192,7 +192,7 @@ org.postgresql postgresql - 9.4.1212 + 42.2.2 provided diff --git a/src/main/java/io/ebean/config/DbMigrationConfig.java b/src/main/java/io/ebean/config/DbMigrationConfig.java index 7e47156b3..f40f6f58d 100644 --- a/src/main/java/io/ebean/config/DbMigrationConfig.java +++ b/src/main/java/io/ebean/config/DbMigrationConfig.java @@ -102,6 +102,11 @@ public class DbMigrationConfig { */ protected Map runPlaceholderMap; + /** + * DB schema used for the migration (and testing). + */ + protected String dbSchema; + /** * DB user used to run the DB migration. */ @@ -373,6 +378,33 @@ public class DbMigrationConfig { this.dbPassword = dbPassword; } + /** + * Return the DB schema to use (for migration, testing etc). + */ + public String getDbSchema() { + String schema = readEnvironment("ddl.migration.schema"); + if (schema != null) { + return schema; + } + return dbSchema; + } + + /** + * Set the Db schema to use. + */ + public void setDbSchema(String dbSchema) { + this.dbSchema = dbSchema; + } + + /** + * Set the Db schema if it hasn't already been defined. + */ + public void setDefaultDbSchema(String dbSchema) { + if (this.dbSchema == null) { + this.dbSchema = dbSchema; + } + } + /** * Return migration versions that should be added to history without running. */ @@ -475,6 +507,7 @@ public class DbMigrationConfig { runMigration = properties.getBoolean("migration.run", runMigration); metaTable = properties.get("migration.metaTable", metaTable); runPlaceholders = properties.get("migration.placeholders", runPlaceholders); + dbSchema = properties.get("migration.dbSchema", dbSchema); //Do not set user and pass from "datasource.db.username" //There is a null test in MigrationRunner::getConnection to handle this @@ -557,6 +590,7 @@ public class DbMigrationConfig { runnerConfig.setRunPlaceholders(runPlaceholders); runnerConfig.setDbUsername(getDbUsername()); runnerConfig.setDbPassword(getDbPassword()); + runnerConfig.setDbSchema(getDbSchema()); runnerConfig.setClassLoader(classLoader); if (patchInsertOn != null) { runnerConfig.setPatchInsertOn(patchInsertOn); diff --git a/src/main/java/io/ebean/config/ServerConfig.java b/src/main/java/io/ebean/config/ServerConfig.java index 0d0bbd70e..088ec3663 100644 --- a/src/main/java/io/ebean/config/ServerConfig.java +++ b/src/main/java/io/ebean/config/ServerConfig.java @@ -296,6 +296,11 @@ public class ServerConfig { */ private DataSourceConfig readOnlyDataSourceConfig = new DataSourceConfig(); + /** + * Optional - the database schema that should be used to own the tables etc. + */ + private String dbSchema; + /** * The db migration config (migration resource path etc). */ @@ -1120,6 +1125,25 @@ public class ServerConfig { this.profilingConfig = profilingConfig; } + /** + * Return the DB schema to use. + */ + public String getDbSchema() { + return dbSchema; + } + + /** + * Set the DB schema to use. This specifies to use this schema for: + *
    + *
  • Running Database migrations - Create and use the DB schema
  • + *
  • Testing DDL - Create-all.sql DDL execution creates and uses schema
  • + *
  • Testing Docker - Set default schema on connection URL
  • + *
+ */ + public void setDbSchema(String dbSchema) { + this.dbSchema = dbSchema; + } + /** * Return the DB migration configuration. */ @@ -2764,6 +2788,10 @@ public class ServerConfig { */ protected void loadSettings(PropertiesWrapper p) { + dbSchema = p.get("dbSchema", dbSchema); + if (dbSchema != null) { + migrationConfig.setDefaultDbSchema(dbSchema); + } profilingConfig.loadSettings(p, name); migrationConfig.loadSettings(p, name); platformConfig.loadSettings(p); diff --git a/src/main/java/io/ebean/config/dbplatform/DatabasePlatform.java b/src/main/java/io/ebean/config/dbplatform/DatabasePlatform.java index ced70893c..ecc211289 100644 --- a/src/main/java/io/ebean/config/dbplatform/DatabasePlatform.java +++ b/src/main/java/io/ebean/config/dbplatform/DatabasePlatform.java @@ -16,6 +16,7 @@ import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.ResultSet; import java.sql.SQLException; +import java.sql.Statement; import java.sql.Types; /** @@ -658,6 +659,39 @@ public class DatabasePlatform { return persistBatchOnCascade; } + /** + * Create the DB schema if it does not exist. + */ + public void createSchemaIfNotExists(String dbSchema, Connection connection) throws SQLException { + if (!schemaExists(dbSchema, connection)) { + Statement query = connection.createStatement(); + try { + logger.info("create schema:{}", dbSchema); + query.executeUpdate("create schema " + dbSchema); + } finally { + JdbcClose.close(query); + } + } + } + + /** + * Return true if the schema exists. + */ + public boolean schemaExists(String dbSchema, Connection connection) throws SQLException { + ResultSet schemas = connection.getMetaData().getSchemas(); + try { + while (schemas.next()) { + String schema = schemas.getString(1); + if (schema.equalsIgnoreCase(dbSchema)) { + return true; + } + } + } finally { + JdbcClose.close(schemas); + } + return false; + } + /** * Return true if the table exists. */ diff --git a/src/main/java/io/ebeaninternal/dbmigration/DdlGenerator.java b/src/main/java/io/ebeaninternal/dbmigration/DdlGenerator.java index 40614c8b8..e8edf79dc 100644 --- a/src/main/java/io/ebeaninternal/dbmigration/DdlGenerator.java +++ b/src/main/java/io/ebeaninternal/dbmigration/DdlGenerator.java @@ -1,8 +1,8 @@ package io.ebeaninternal.dbmigration; -import io.ebean.Transaction; import io.ebean.config.ServerConfig; import io.ebean.migration.ddl.DdlRunner; +import io.ebean.util.JdbcClose; import io.ebeaninternal.api.SpiEbeanServer; import io.ebeaninternal.dbmigration.model.CurrentModel; import io.ebeaninternal.extraddl.model.ExtraDdlXmlReader; @@ -38,6 +38,7 @@ public class DdlGenerator { private final boolean createOnly; private final boolean jaxbPresent; private final boolean ddlCommitOnCreateIndex; + private final String dbSchema; private CurrentModel currentModel; private String dropAllContent; @@ -48,6 +49,7 @@ public class DdlGenerator { this.jaxbPresent = serverConfig.getClassLoadConfig().isJavaxJAXBPresent(); this.generateDdl = serverConfig.isDdlGenerate(); this.createOnly = serverConfig.isDdlCreateOnly(); + this.dbSchema = serverConfig.getDbSchema(); if (!serverConfig.getTenantMode().isDdlEnabled() && serverConfig.isDdlRun()) { log.warn("DDL can't be run on startup with TenantMode " + serverConfig.getTenantMode()); this.runDdl = false; @@ -85,30 +87,54 @@ public class DdlGenerator { * Run the DDL drop and DDL create scripts if properties have been set. */ protected void runDdl() { - if (runDdl) { + Connection connection = null; try { - runInitSql(); - runDropSql(); - runCreateSql(); - runSeedSql(); - - } catch (IOException e) { - String msg = "Error reading drop/create script from file system"; - throw new RuntimeException(msg, e); + connection = obtainConnection(); + runDdlWith(connection); + } finally { + JdbcClose.rollback(connection); + JdbcClose.close(connection); } } } + private void runDdlWith(Connection connection) { + try { + if (dbSchema != null) { + createSchemaIfRequired(connection); + } + runInitSql(connection); + runDropSql(connection); + runCreateSql(connection); + runSeedSql(connection); + } catch (IOException e) { + throw new RuntimeException("Error reading drop/create script from file system", e); + } + } + + private Connection obtainConnection() { + try { + return server.getPluginApi().getDataSource().getConnection(); + } catch (SQLException e) { + throw new PersistenceException("Failed to obtain connection to run DDL", e); + } + } + + private void createSchemaIfRequired(Connection connection) { + try { + server.getDatabasePlatform().createSchemaIfNotExists(dbSchema, connection); + } catch (SQLException e) { + throw new PersistenceException("Failed to create DB Schema", e); + } + } + /** * Execute all the DDL statements in the script. */ - public int runScript(boolean expectErrors, String content, String scriptName) { + public int runScript(Connection connection, boolean expectErrors, String content, String scriptName) { DdlRunner runner = new DdlRunner(expectErrors, scriptName); - - Transaction transaction = server.createTransaction(); - Connection connection = transaction.getConnection(); try { if (expectErrors) { connection.setAutoCommit(true); @@ -119,64 +145,63 @@ public class DdlGenerator { if (expectErrors) { connection.setAutoCommit(false); } - transaction.commit(); + connection.commit(); return count; } catch (SQLException e) { throw new PersistenceException("Failed to run script", e); - } finally { - transaction.end(); + JdbcClose.rollback(connection); } } - protected void runDropSql() throws IOException { + protected void runDropSql(Connection connection) throws IOException { if (!createOnly) { String ignoreExtraDdl = System.getProperty("ebean.ignoreExtraDdl"); if (!"true".equalsIgnoreCase(ignoreExtraDdl) && jaxbPresent) { String extraApply = ExtraDdlXmlReader.buildExtra(server.getDatabasePlatform().getName(), true); if (extraApply != null) { - runScript(false, extraApply, "extra-dll"); + runScript(connection, false, extraApply, "extra-dll"); } } if (dropAllContent == null) { dropAllContent = readFile(getDropFileName()); } - runScript(true, dropAllContent, getDropFileName()); + runScript(connection, true, dropAllContent, getDropFileName()); } } - protected void runCreateSql() throws IOException { + protected void runCreateSql(Connection connection) throws IOException { if (createAllContent == null) { createAllContent = readFile(getCreateFileName()); } - runScript(false, createAllContent, getCreateFileName()); + runScript(connection, false, createAllContent, getCreateFileName()); String ignoreExtraDdl = System.getProperty("ebean.ignoreExtraDdl"); if (!"true".equalsIgnoreCase(ignoreExtraDdl) && jaxbPresent) { String extraApply = ExtraDdlXmlReader.buildExtra(server.getDatabasePlatform().getName(), false); if (extraApply != null) { - runScript(false, extraApply, "extra-dll"); + runScript(connection, false, extraApply, "extra-dll"); } } } - protected void runInitSql() throws IOException { - runResourceScript(server.getServerConfig().getDdlInitSql()); + protected void runInitSql(Connection connection) throws IOException { + runResourceScript(connection, server.getServerConfig().getDdlInitSql()); } - protected void runSeedSql() throws IOException { - runResourceScript(server.getServerConfig().getDdlSeedSql()); + protected void runSeedSql(Connection connection) throws IOException { + runResourceScript(connection, server.getServerConfig().getDdlSeedSql()); } - protected void runResourceScript(String sqlScript) throws IOException { + protected void runResourceScript(Connection connection, String sqlScript) throws IOException { if (sqlScript != null) { try (InputStream is = getClassLoader().getResourceAsStream(sqlScript)) { if (is != null) { String content = readContent(new InputStreamReader(is)); - runScript(false, content, sqlScript); + runScript(connection, false, content, sqlScript); } } }