001package io.ebean.migration.ddl; 002 003import org.slf4j.Logger; 004import org.slf4j.LoggerFactory; 005 006import java.io.StringReader; 007import java.sql.Connection; 008import java.sql.PreparedStatement; 009import java.sql.SQLException; 010import java.util.ArrayList; 011import java.util.List; 012 013/** 014 * Runs DDL scripts. 015 */ 016public class DdlRunner { 017 018 protected static final Logger logger = LoggerFactory.getLogger("io.ebean.DDL"); 019 020 private final DdlParser parser; 021 022 private final String scriptName; 023 024 private final boolean useAutoCommit; 025 026 /** 027 * Construct with a script name (for logging) and flag indicating if 028 * auto commit is used (in which case errors are allowed). 029 */ 030 public DdlRunner(boolean useAutoCommit, String scriptName) { 031 this(useAutoCommit, scriptName, DdlAutoCommit.NONE); 032 } 033 034 /** 035 * Create specifying the database platform by name. 036 */ 037 public DdlRunner(boolean useAutoCommit, String scriptName, String platformName) { 038 this(useAutoCommit, scriptName, DdlAutoCommit.forPlatform(platformName)); 039 } 040 041 /** 042 * Create specifying the auto commit behaviour (for the database platform). 043 */ 044 public DdlRunner(boolean useAutoCommit, String scriptName, DdlAutoCommit ddlAutoCommit) { 045 this.useAutoCommit = useAutoCommit || ddlAutoCommit.isAutoCommit(); 046 this.scriptName = scriptName; 047 this.parser = new DdlParser(this.useAutoCommit ? DdlAutoCommit.NONE : ddlAutoCommit); 048 } 049 050 /** 051 * Parse the content into sql statements and execute them in a transaction. 052 * 053 * @return The non-transactional statements that should execute later. 054 */ 055 public List<String> runAll(String content, Connection connection) throws SQLException { 056 List<String> statements = parser.parse(new StringReader(content)); 057 runStatements(statements, connection); 058 return parser.getNonTransactional(); 059 } 060 061 /** 062 * Execute all the statements in a single transaction. 063 */ 064 private void runStatements(List<String> statements, Connection connection) throws SQLException { 065 066 List<String> noDuplicates = new ArrayList<>(); 067 for (String statement : statements) { 068 if (!noDuplicates.contains(statement)) { 069 noDuplicates.add(statement); 070 } 071 } 072 if (noDuplicates.isEmpty()) { 073 return; 074 } 075 boolean setAutoCommit = useAutoCommit && !connection.getAutoCommit(); 076 if (setAutoCommit) { 077 connection.setAutoCommit(true); 078 } 079 try { 080 logger.info("Executing {} - {} statements, autoCommit:{}", scriptName, noDuplicates.size(), useAutoCommit); 081 for (int i = 0; i < noDuplicates.size(); i++) { 082 String xOfy = (i + 1) + " of " + noDuplicates.size(); 083 String ddl = noDuplicates.get(i); 084 runStatement(xOfy, ddl, connection); 085 } 086 } finally { 087 if (setAutoCommit) { 088 connection.setAutoCommit(false); 089 } 090 } 091 } 092 093 /** 094 * Execute the statement. 095 */ 096 private void runStatement(String oneOf, String stmt, Connection c) throws SQLException { 097 // trim and remove trailing ; or / 098 stmt = stmt.trim(); 099 if (stmt.endsWith(";")) { 100 stmt = stmt.substring(0, stmt.length() - 1); 101 } else if (stmt.endsWith("/")) { 102 stmt = stmt.substring(0, stmt.length() - 1); 103 } 104 if (stmt.isEmpty()) { 105 logger.debug("skip empty statement at " + oneOf); 106 return; 107 } 108 if (logger.isDebugEnabled()) { 109 logger.debug("executing " + oneOf + " " + getSummary(stmt)); 110 } 111 112 try (PreparedStatement statement = c.prepareStatement(stmt)) { 113 statement.execute(); 114 } catch (SQLException e) { 115 if (useAutoCommit) { 116 logger.debug(" ... ignoring error executing " + getSummary(stmt) + " error: " + e.getMessage()); 117 } else { 118 throw new SQLException("Error executing stmt[" + stmt + "] error[" + e.getMessage() + "]", e); 119 } 120 } 121 } 122 123 private String getSummary(String s) { 124 if (s.length() > 80) { 125 return s.substring(0, 80).trim().replace('\n', ' ') + "..."; 126 } 127 return s.replace('\n', ' '); 128 } 129 130 /** 131 * Run any non-transactional statements from the just parsed script. 132 */ 133 public int runNonTransactional(Connection connection) { 134 final List<String> nonTransactional = parser.getNonTransactional(); 135 return !nonTransactional.isEmpty() ? runNonTransactional(connection, nonTransactional) : 0; 136 } 137 138 /** 139 * Run the non-transactional statements with auto commit true. 140 */ 141 public int runNonTransactional(Connection connection, List<String> nonTransactional) { 142 int count = 0; 143 String sql = null; 144 try { 145 logger.debug("running {} non-transactional migration statements", nonTransactional.size()); 146 connection.setAutoCommit(true); 147 for (int i = 0; i < nonTransactional.size(); i++) { 148 sql = nonTransactional.get(i); 149 try (PreparedStatement statement = connection.prepareStatement(sql)) { 150 logger.debug("executing - {}", sql); 151 statement.execute(); 152 count++; 153 } 154 } 155 return count; 156 157 } catch (SQLException e) { 158 logger.error("Error running non-transaction migration: " + sql, e); 159 return count; 160 } finally { 161 try { 162 connection.setAutoCommit(false); 163 } catch (SQLException e) { 164 logger.error("Error resetting connection autoCommit to false", e); 165 } 166 } 167 } 168}